#include "rate_controller.hh"
#include <iostream>
#include <boost/bind.hpp>
#include <boost/shared_array.hpp>
#include "authenticator/flow-in.hh"
#include <flow-expired.hh>
#include "vlog.hh"
#include "assert.hh"
#include <time.h>

#include <boost/lexical_cast.hpp>

#define FLOW_TIMEOUT 5
#define MAX_PATH_LEN 10
#define BROADCAST_TIMEOUT 60
#define LINK_RATE 512*1024
#define DP_FROM_AP(loc) ((loc) & 0xffffffffffffULL)

using namespace std;
using namespace vigil::container;

namespace vigil {
namespace applications {

static Vlog_module lg("rate_controller");

rate_controller::rate_controller(const Context* c, const xercesc::DOMNode* d)
	:Component(c) , linkdb(0) , routing(0), empty(new Routing_module::Route())
{
	// do nothing
}

rate_controller::~rate_controller()  
{
        // do nothing
}
uint32_t rate_controller::hash_flow(ofp_match *ofm){
	uint32_t x;
	ethernetaddr srcmac(ofm->dl_src);
	x = vigil::fnv_hash(&srcmac, sizeof(ethernetaddr));
	ethernetaddr dstmac(ofm->dl_dst);
	x = vigil::fnv_hash(&dstmac, sizeof(ethernetaddr), x);
	x = vigil::fnv_hash(&ofm->dl_vlan, sizeof(uint16_t), x);
	x = vigil::fnv_hash(&ofm->dl_type, sizeof(uint16_t), x);
	x = vigil::fnv_hash(&ofm->nw_proto, sizeof(uint8_t), x);
	x = vigil::fnv_hash(&ofm->nw_src, sizeof(uint32_t), x);
	x = vigil::fnv_hash(&ofm->nw_dst, sizeof(uint32_t), x);
	x = vigil::fnv_hash(&ofm->tp_src, sizeof(uint16_t), x);
	x = vigil::fnv_hash(&ofm->tp_dst, sizeof(uint16_t), x);
	return x;
}
uint32_t rate_controller::hash_flow(Flow flow){
	uint32_t x;        
	x = vigil::fnv_hash(&flow.dl_src, sizeof(ethernetaddr));
	x = vigil::fnv_hash(&flow.dl_dst, sizeof(ethernetaddr), x);
	x = vigil::fnv_hash(&flow.dl_vlan, sizeof(uint16_t), x);
	x = vigil::fnv_hash(&flow.dl_type, sizeof(uint16_t), x);
	x = vigil::fnv_hash(&flow.nw_proto, sizeof(uint8_t), x);
	x = vigil::fnv_hash(&flow.nw_src, sizeof(uint32_t), x);
	x = vigil::fnv_hash(&flow.nw_dst, sizeof(uint32_t), x);
	x = vigil::fnv_hash(&flow.tp_src, sizeof(uint16_t), x);
	x = vigil::fnv_hash(&flow.tp_dst, sizeof(uint16_t), x);
	return x;
}

void rate_controller::configure(const Configuration* config)
{
	register_handler<Flow_in_event> (boost::bind(&rate_controller::handle_flow_in, this, _1));
	register_handler<Flow_expired_event> (boost::bind(&rate_controller::handle_flow_expired, this, _1));
	resolve(routing);
	resolve(linkdb);
}

Disposition rate_controller::handle_flow_expired(const Event& e){
	const Flow_expired_event& fee =  dynamic_cast<const Flow_expired_event&>(e);
	datapathid datapath_id = fee.datapath_id;
	ofp_match *ofm = const_cast<ofp_match*>(fee.get_flow());
	uint32_t hash_value = hash_flow(ofm);
	delRateEntry(hash_value);
	return CONTINUE;
}
bool rate_controller::addRateEntry(uint32_t hash_value, uint64_t rate, datapathid dp, uint16_t port){
	RateEntryPtr rep(new RateEntry);
	rep->rate = rate;
	rep->dp = dp;
	rep->inport = port;
	return addRateEntry(hash_value, rep);
}

bool rate_controller::addRateEntry(uint32_t hash_value, RateEntryPtr new_entry){
	rate_table[hash_value] = new_entry;
	return true;
}

bool rate_controller::delRateEntry(uint32_t hash_value){
	RateTable::iterator rtit = rate_table.find(hash_value);
	if( rtit != rate_table.end()){
		rate_table.erase(rtit);
		return true;
	}
	return false;
}

bool rate_controller::updateRate(uint32_t hash_value, uint64_t rate)
{
	RateTable::iterator rtit = rate_table.find(hash_value);
	if( rtit != rate_table.end()){
		RateEntryPtr rep = rtit->second;
		rep->rate = rate;
		return true;
	}
	return false;
}
uint64_t rate_controller::getRate(uint32_t hash_value){
	
	RateTable::iterator rtit = rate_table.find(hash_value);
	
	if( rtit != rate_table.end()){
		RateEntryPtr rep = rtit->second;
		return rep->rate;
	}
	return 0;
}
bool rate_controller::getRateEntry(uint32_t hash_value, RateEntryPtr& rep){
	
	RateTable::iterator rtit = rate_table.find(hash_value);
	
	if( rtit != rate_table.end()){
		rep = rtit->second;
		return true;
	}
	return false;
}

Disposition rate_controller::handle_flow_in(const Event& e)
{

	Flow_in_event& fi = const_cast<Flow_in_event&>(dynamic_cast<const Flow_in_event&>(e));

        cout << "###FLOW IN EVENT### info: dl_type " << ntohs(fi.flow.dl_type) << " dl_src: " << fi.flow.dl_src 
	     << " dl_dst: " << fi.flow.dl_dst << " dst_ip: " << fi.flow.nw_dst << " nw_proto: " << (int)fi.flow.nw_proto 
	     << " in port: " << ntohs(fi.flow.in_port) << " dpid: " << fi.datapath_id.as_host() << endl;
	
    	Routing_module::RoutePtr route;
    	uint16_t inport, outport;


	Buffer *actions = new Nonowning_buffer();
    	if (!set_route(fi, route, inport, outport, *actions, true)) {
		cout << "set route returned error" << endl;
	        return CONTINUE;
	}

	Routing_module::ActionList alist;
	if (actions->size() > 0) {
        	alist.resize(route->path.size());
        	alist.push_front(*actions);
    	}

	/****** OUR CODE! ******/

        setup_route(fi.flow, *route, inport, outport, FLOW_TIMEOUT);


        bool on_route = (fi.datapath_id == route->id.src)
        || (fi.datapath_id == route->id.dst);
	cout << "fi.dpif " << fi.datapath_id.as_host() << " route src " << route->id.src.as_host() << " route dst " << 
		route->id.dst.as_host() << endl;
        if (!on_route) {
                for (std::list<Routing_module::Link>::const_iterator link = route->path.begin();
                        link != route->path.end(); ++link)
                {
                        if (fi.datapath_id == link->dst) {
                                on_route = true;
                                break;
                        }
                }
        }

        if (!on_route) {
		cout << "ROUTING ERROR! SHOULDN'T HAPPEN" << endl;
		return CONTINUE;
        }

	
        // just do rate control for IP packets
        if (fi.flow.dl_type == htons(0x0800)) {
		cout << "Flow in event had type 800H" << endl;
		datapathid dp = route->id.src;
		uint16_t r_inport = inport;
		uint16_t r_outport;


		uint64_t Agg_flow_rates[MAX_PATH_LEN];
		uint64_t remainingBWs[MAX_PATH_LEN];
		int cnt =0;

		// first traverse to find rate:
		uint32_t fhash = hash_flow(fi.flow);
		cout << "first traverse starts" << endl;
		uint64_t rate = LINK_RATE;
		list<Routing_module::Link>::const_iterator link = route->path.begin();
		while (true) {
			if (link == route->path.end()) {
				r_outport = outport;
			} else {
				r_outport = link-> outport;
			}

			// find fair share:
			int size_link = linkdb->linkFlowNum(dp, r_outport);
			uint64_t equal_share = LINK_RATE/size_link;
			cout << "equal share: " << equal_share << endl;

			// find remaining BW on this link
              		list<FlowEntryPtr> thisLink = linkdb->getFlowList(dp, r_outport);
               		list<FlowEntryPtr>::iterator lit = thisLink.begin();
			uint64_t usedBW = 0;
			uint64_t def_from_equal = 0;
			uint64_t num_aggresive = 0;
                	for (; lit!= thisLink.end(); lit++){
				cout << "next link BW: " << getRate((*lit)->flow_hash) << endl;
				uint64_t lr = getRate((*lit)->flow_hash);
				cout << "lr " << lr << endl;;
                        	usedBW += lr;
				if (lr <= equal_share && lr>0) {
					def_from_equal += ( equal_share - lr);
				} else {
					num_aggresive++;
				}
               		}
			uint64_t remainingBW = LINK_RATE - usedBW;
			cout << "remaining BW: " << remainingBW << endl;
			uint64_t Agg_flow_rate = equal_share + (def_from_equal/(num_aggresive+1));
                	Agg_flow_rates[cnt] = Agg_flow_rate;
                	remainingBWs[cnt] = remainingBW;
			uint64_t thisLinkBW = (remainingBW > Agg_flow_rate) ? remainingBW : Agg_flow_rate;
			rate = (thisLinkBW > rate)? rate : thisLinkBW;

                        if( link == route->path.end()){
                                break;
                        }

                        dp = link->dst;
                        r_inport = link->inport;
                        ++link;
			cnt++;
			
		}
		cout << "Rate for new flow should be: " << rate << endl;

                // second traverse: set link rate, first_dpid and first in_port and send ratectrl messages
                link = route->path.begin();
                dp = route->id.src;
                r_inport = inport;
		cout << "start of traverse 2" << endl;
		cnt = 0;
                while (true) {
                        if (link == route->path.end()) {
                                r_outport = outport;
                        } else {
                                r_outport = link-> outport;
                        }

		 //only affect those links that doesn't have enough spare BW
		     if (rate > remainingBWs[cnt]) {
			
                        // set rate, dpid and in_port.
                        list<FlowEntryPtr> thisLink = linkdb->getFlowList(dp, r_outport);
                        list<FlowEntryPtr>::iterator lit = thisLink.begin();

                        for (; lit!= thisLink.end(); lit++){
                                if (fhash != (*lit)->flow_hash) {
					cout << "Going to check if it should check rate for old fow" << endl;
					// entry that added previously
					if (getRate((*lit)->flow_hash) > Agg_flow_rates[cnt]) {
						cout << "after checking old flow, we have to change rate for " 
							<< (*lit)->flow_hash << " to " << Agg_flow_rates[cnt] <<  endl;
						updateRate((*lit)->flow_hash, Agg_flow_rates[cnt]);
                                        	Flow n_flow = linkdb->create_flow_instance(*lit);
						RateEntryPtr ptr;
						if (!getRateEntry((*lit)->flow_hash, ptr)) {
							cout << "Warning: flowdb and rate entry not sync" << endl;
							continue;
						}
						cout << "verify that updated rate is as expected: " << ptr->rate << endl;
						n_flow.in_port = ptr->inport;
						cout << "&&& send rate cntrl message for old entry &&&" << endl;
                                        	reportRate(ptr->dp, n_flow, ptr->rate);
					}
				}
                        }
		     }
                        

                        if( link == route->path.end()){
                                break;
                        }

                        dp = link->dst;
                        r_inport = link->inport;
                        ++link;
			cnt++;

                }

		cout << "&&& send rate cntrl message for new entry &&&" << endl;
		addRateEntry(fhash, rate, fi.datapath_id, fi.flow.in_port);
		reportRate(fi.datapath_id, fi.flow, rate);

	}


	/***********************/


        routing->send_packet(fi.datapath_id, ntohs(fi.flow.in_port),
                       OFPP_TABLE, fi.buffer_id, *(fi.buf),
                       Nonowning_buffer(), false,
                       fi.flow, NULL, NULL);

		
	return CONTINUE;
}

void rate_controller::reportRate(const datapathid& dpid, const Flow& flow, uint64_t rate)
{
	unsigned char sender[6] = {0x50,0x55,0x60,0x65,0x70,0x75};
        boost::shared_array<char> msg_raw(new char[80]);
	RateMsg *Rmsg = (RateMsg*) msg_raw.get();
        Rmsg->src_mac = ethernetaddr(sender);
        Rmsg->dst_mac = flow.dl_src;
        Rmsg->type = htons(RATE_MSG_ETHR_TYPE);
        Rmsg->ver = htons(CUR_VERSION);
        Rmsg->src_ip = flow.nw_src;
        Rmsg->dst_ip = flow.nw_dst;
	Rmsg->pad = 0;
	Rmsg->proto_tp = flow.nw_proto;
        Rmsg->src_tp = flow.tp_src;
        Rmsg->dst_tp = flow.tp_dst;
	Rmsg->rate = rate;


	cout << "****New Flow In->rate control****" << endl;
	cout << "src: " << Rmsg->src_mac <<  " dst: " << Rmsg->dst_mac << " " ;
	cout << "type: " << ntohs(flow.dl_type) << " in_port: " << ntohs(flow.in_port) << " ip-proto: " 
		<< (int)flow.nw_proto << endl;
	cout << "-stp and dtp: " << ntohs(flow.tp_src) << " " << ntohs(flow.tp_dst) << endl;

	Nonowning_buffer toBeSent((uint8_t*)msg_raw.get(), sizeof(RateMsg));

	nox::send_openflow_packet_out(dpid, toBeSent, htons(flow.in_port), 0xffff, true);

	cout << "...rate control msg(" << sizeof(RateMsg) << "B)  sent to port " << htons(flow.in_port) << endl;
	cout << "RATE will be: " << rate << endl;
}
bool
rate_controller::setup_route(Flow flow, Routing_module::Route route, 
		       uint16_t ap_inport, uint16_t ap_outport,
                       uint16_t flow_timeout)
{


	ofp_flow_mod *ofm;
        size_t size = sizeof *ofm + sizeof(ofp_action_output);
        boost::shared_array<char> raw_of(new char[size]);
        ofm = (ofp_flow_mod*) raw_of.get();

        ofm->header.version = OFP_VERSION;
        ofm->header.type = OFPT_FLOW_MOD;
        ofm->match.wildcards = htonl(0);
        ofm->match.dl_vlan = flow.dl_vlan;
        memcpy(ofm->match.dl_src, flow.dl_src.octet, sizeof ofm->match.dl_src);
        memcpy(ofm->match.dl_dst, flow.dl_dst.octet, sizeof ofm->match.dl_dst);
        ofm->match.dl_type = flow.dl_type;
        ofm->match.nw_src = flow.nw_src;
        ofm->match.nw_dst = flow.nw_dst;
        ofm->match.nw_proto = flow.nw_proto;
        ofm->match.tp_src = flow.tp_src;
        ofm->match.tp_dst = flow.tp_dst;
        ofm->buffer_id = htonl(UINT32_MAX);
        ofm->idle_timeout = htons(FLOW_TIMEOUT);
        ofm->hard_timeout = htons(OFP_FLOW_PERMANENT);
        ofm->priority = htons(OFP_DEFAULT_PRIORITY);
        ofm->reserved = htonl(0);

        ofm->header.length = htons(size);
        ofm->command = htons(OFPFC_ADD);
        

	datapathid dp = route.id.src;
	uint16_t outport, inport = ap_inport;
	outport = ap_outport;
	ofp_action_output *action = (ofp_action_output*) (((uint8_t*)ofm->actions) + 0);
	std::list<Routing_module::Link>::const_reverse_iterator link = route.path.rbegin();
	
	printf("PATH SIZE:%d\n",route.path.size());
	if( route.path.size() == 0){
		inport = ap_inport;
		outport = ap_outport;
		dp = route.id.src;
		
		ofm->match.in_port = htons(inport);

		memset(action, 0, sizeof(ofp_action_output));
		action->type = htons(OFPAT_OUTPUT);
		action->len = htons(sizeof(ofp_action_output));
		action->max_len = htons(0);
		action->port = htons(outport);
	
		//printf("CMD: ADD SOLO FLOW ENTRY @ %"PRIx64" in:%"PRIu16" out:%"PRIu16"\n", 
		//				dp.as_host(), inport, outport);
		send_openflow_command(dp, &ofm->header, false);
		//printf("SENT CMD\n");
	}else{
		while(true){
			if( link == route.path.rend() ){
				inport = ap_inport;
				dp = route.id.src;
			} else {
				inport = link->inport;
				dp = link->dst;
			}
			ofm->match.in_port = htons(inport);

			memset(action, 0, sizeof(ofp_action_output));
			action->type = htons(OFPAT_OUTPUT);
			action->len = htons(sizeof(ofp_action_output));
			action->max_len = htons(0);
			action->port = htons(outport);

			//printf("CMD: ADD FLOW ENTRY @ %"PRIx64" in:%"PRIu16" out:%"PRIu16"\n", 
			//			dp.as_host(), inport, outport);
			send_openflow_command(dp, &ofm->header, false);

			if( link == route.path.rend() ){
				break;
			}

			outport = link->outport;
			++link;
		}//end while
	}//end route size > 0

	//printf("Finish Setup Route!\n");
	return true;
}
bool
rate_controller::set_route(Flow_in_event& fi,
                     Routing_module::RoutePtr& route,
                     uint16_t& inport, uint16_t& outport,
                     const Buffer& actions, bool check_nat)
{
    if (fi.route_source == NULL) {
        empty->id.src = datapathid::from_host(DP_FROM_AP(fi.source->location));
        inport = (uint16_t)(fi.source->location >> 48);
    } else {
        empty->id.src = datapathid::from_host(DP_FROM_AP(fi.route_source->location));
        inport = (uint16_t)(fi.route_source->location >> 48);
    }
    bool use_dst;
    fi.routed_to = 0;
    ConnList::const_iterator route_conn;

    if (fi.route_destinations.empty()) {
        use_dst = true;
    } else {
        use_dst = false;
        route_conn = fi.route_destinations.begin();
    }
    uint64_t location;
    bool checked = false;
    while (true) {
        if (use_dst) {
            if (fi.routed_to >= fi.destinations.size()) {
                break;
            }
            const Flow_in_event::DestinationInfo& dst = fi.destinations[fi.routed_to];
            if (dst.allowed) {
                location = dst.connector->location;
            } else {
                location = 0;
            }
        } else if (route_conn == fi.route_destinations.end()) {
            break;
        } else {
            location = (*route_conn)->location;
        }

        if (location != 0) {
            bool check = false;
            empty->id.dst = datapathid::from_host(DP_FROM_AP(location));
            if (empty->id.src == empty->id.dst) {
                route = empty;
                check = true;
            } else {
                check = routing->get_route(empty->id, route);
            }

            if (check) {
                checked = true;
                outport = (uint16_t) (location >> 48);
                if (routing->check_route(*route, inport, outport)) {
                    return true;
                }
                VLOG_DBG(lg, "Invalid route between aps %"PRIx64":%"PRIu16" and %"PRIx64":%"PRIu16".",
                          empty->id.src.as_host(), inport, empty->id.dst.as_host(), outport);
            } else {
                VLOG_DBG(lg, "No route found between dps %"PRIx64" and %"PRIx64".",
                          empty->id.src.as_host(), empty->id.dst.as_host());
            }
        }

        ++fi.routed_to;
        if (!use_dst) {
            ++route_conn;
        }
    }

//   If failed on check_route(), don't route...correct?
    if (checked) {
        fi.routed_to = Flow_in_event::NOT_ROUTED;
        VLOG_WARN(lg, "Could not find valid route to any destination.");
        std::ostringstream os;
        os << fi.flow;
        VLOG_WARN(lg, "Dropping %s", os.str().c_str());
        return false;
    }

    if (lg.is_dbg_enabled()) {
        std::ostringstream os;
        os << fi.flow;
        VLOG_DBG(lg, "Broadcasting %s", os.str().c_str());
    }

    fi.routed_to = Flow_in_event::BROADCASTED;
    if (fi.flow.dl_dst.is_broadcast()) {
	cout << "Going to Broad cast" << endl;
        routing->setup_flow(fi.flow, fi.datapath_id, OFPP_FLOOD,
                            fi.buffer_id, *(fi.buf), BROADCAST_TIMEOUT,
                            actions, fi.flow.dl_type == ethernet::IP && check_nat,
                            fi.src_addr_groups.get(), fi.dst_addr_groups.get());
    } else {
	cout << "Going to Broad cast" << endl;
        routing->send_packet(fi.datapath_id, ntohs(fi.flow.in_port), OFPP_FLOOD,
                             fi.buffer_id, *(fi.buf), actions,
                             fi.flow.dl_type == ethernet::IP && check_nat, fi.flow,
                             fi.src_addr_groups.get(), fi.dst_addr_groups.get());
    }

    return false;
}
void rate_controller::getInstance(const container::Context* ctxt, rate_controller*& ofp){
		ofp = dynamic_cast<rate_controller*>
			(ctxt->get_by_interface(container::Interface_description(typeid(rate_controller).name())));
	}
}
}

REGISTER_COMPONENT(vigil::container::Simple_component_factory
                <vigil::applications::rate_controller>,           
                vigil::applications::rate_controller);
